
package horde.utils;

import java.lang.reflect.Method;
import javax.swing.JOptionPane;


/**
 * @author Andres Picazo Cuesta
 */
public class CallMethodByName {

    /**
     * List the public methods avaiable from the source object
     * @param source The object to obtain the parameters
     * @return A string array, each position has one method
     */
    public static String [] listMethods (Object source) {
        Method [] methods = source.getClass().getDeclaredMethods();
        String [] list = new String[methods.length];
        for (int i = 0; i < methods.length; i++) {
            String currentMethod = "[Method]: " + methods[i].getName() + "\n[Params]: ";
            Class [] parameters = methods[i].getParameterTypes();
            if (parameters.length == 0) currentMethod += "None";
            else
                for (int k = 0; k < parameters.length; k++)
                    currentMethod = currentMethod + parameters[k].getName() + "\t";
            list[i] = currentMethod + "\n";
        }
        return list;
    }

    /**
     * Checks if the parameters are the same, so the method can be called
     * @param obtainedTypes The parameters types i have obtained from the list
     * @param intendedTypes The parameters i have to match to return true
     * @return True if the obtained and intendes parameters are compatible
     */
    private static boolean checkParameterTypes (Class [] obtainedTypes, Object [] intendedTypes) {
        if (obtainedTypes.length != intendedTypes.length) return false;
        else {
            boolean matches = true;
            int iterator = 0;
            while (iterator < obtainedTypes.length && matches) {
                String obtained = obtainedTypes[iterator].getCanonicalName();
                String intended = intendedTypes[iterator].getClass().getCanonicalName();
                if (!obtained.equals(intended)) matches = false;
                iterator++;
            }
            return matches;
        }
    }

    /**
     * Prints to stdout the result of calling listMethods(source)
     * @param source The object whose parameters i want to print
     * @see listMethods (Object source)
     */
    public static void printListOfMethods (Object source) {
        String [] methods = listMethods(source);
        for (String next : methods) System.out.println (next);
    }

    /**
     * Looks for a public method in the methodContainer, with the name methodName
     * and the parameters listed in methodParameters
     * @param methodName The name of the method to find
     * @param methodParameters The parameters i want to call the method with
     * @param methodContainer The class where i want to find the method
     * @return The method i can call with the given parameters, or null if none is avaiable
     */
    public static Method getMethod (String methodName, Object [] methodParameters, Object methodContainer) {
        Method [] methods = methodContainer.getClass().getDeclaredMethods();
        boolean matches = false;
        int iterator = 0;
        while (iterator < methods.length && !matches) {
            if (methods[iterator].getName().equals(methodName)) {
                /* Gets the parameter types for the candidate method */
                Class [] obtainedTypes = methods[iterator].getParameterTypes();
                /* I have no parameter types to check */
                if (obtainedTypes.length == 0 && methodParameters == null) matches = true;
                else if (checkParameterTypes(obtainedTypes, methodParameters)) matches = true;
                else iterator++;
            }
            else iterator++;
        }
        if (matches) return methods[iterator];
        else return null;
    }

    /**
     * Tries to call the requested method
     * @param methodName The method to call
     * @param methodParameters The parameters to call the method with
     * @param methodContainer Where is supossed to be the method
     * @return An int value with the call status
     */
    public static int call (String methodName, Object [] methodParameters, Object methodContainer) {
        Method method;
        /* Find a valid method to call */
        try {
            method = getMethod (methodName, methodParameters, methodContainer);
        }
        catch (Exception methodNotFound) {
            return METHOD_NOT_FOUND;
        }
        /* Call the method found */
        try {
            if (method != null && methodParameters == null) {
                method.invoke(methodContainer);
                return METHOD_CALLED;
            }
            else if (method != null) {
                method.invoke(methodContainer, methodParameters);
                return METHOD_CALLED;
            }
            else return METHOD_NOT_FOUND;
        }
        catch (Exception methodCallFails) {
            return METHOD_CALL_FAILED;
        }
    }

    public void test () {
        foo source = new foo();
        printListOfMethods (source);
        System.out.println ("Calling a method (Should show a message)");

        Object [] params = {"Test message"};
        int exitValue = call ("printValue", params, source);
        System.err.println (getExitValueMeaning (exitValue));

        
        exitValue = call ("showMessage", params, source);
        System.err.println (getExitValueMeaning (exitValue));

        params[0] = 7;
        exitValue = call ("printValue", params, source);
        System.err.println (getExitValueMeaning (exitValue));
    }

    /**
     * Gets a string with the meaning for an exit value
     * @param exitValue The value to be interpreted
     * @return String with the interpretation
     */
    public static String getExitValueMeaning (int exitValue) {
        String meaning = "";
        switch (exitValue) {
            case METHOD_NOT_FOUND : meaning = "method not found"; break;
            case METHOD_CALLED : meaning = "method sucessfully called"; break;
            case METHOD_CALL_FAILED : meaning = "method call failed"; break;
            case METHOD_NOT_REQUESTED_YET : meaning = "no method has been called yet"; break;
            default : meaning = "Unknown exit value"; break;
        }
        return meaning;
    }
    /* Code only for testing purposes */
    private static class foo {
        public foo () {}
        public void showMessage (String message) {
            JOptionPane.showMessageDialog(null, message);
        }
        public void printValue (String value) {
            System.out.println ("I got the string : " + value);
        }
        public void printValue (Integer value) {
            System.out.println ("I got the integer : " + value);
        }
    }

    /* Return values */
    public static final int METHOD_NOT_REQUESTED_YET = 0;
    public static final int METHOD_NOT_FOUND = 1;
    public static final int METHOD_CALLED = 2;
    public static final int METHOD_CALL_FAILED = 3;
}
